//============================================================================
// Name        : Login.cpp
// Author      : zhou zhao
// Version     :
// Copyright   : Your copyright notice
// Description : Hello World in C++, Ansi-style
//============================================================================

#include <iostream>
#include <vector>

#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>

#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>

#include "ServerRecord.h"
#define SERVER_PORT "3490" //port number the server will listening to
#define SUPERNODE_INFO "Accepted#127.0.0.1 3500" //IP and port number of supernode the server will send
#define SUPER_PORT "3600"
#define BACKLOG 10 //the maximum pending connections in the queue

#define MAXLINE 100
#define MAXDATASIZE 100
using namespace std;

vector<ServerRecord*> userDB;
/*
//callback function when parent process receive signal from the child
void sigchld_handler(int signal){
	while(waitpid(-1, NULL, WNOHANG) > 0);
}
*/

//translate general socket address to IPv4 or IPv6 address
void* sockAddrTranslate(struct sockaddr* sockAddr){
	if(sockAddr->sa_family == AF_INET){
		return &(((struct sockaddr_in*)sockAddr)->sin_addr);
	}else{
		return &(((struct sockaddr_in6*)sockAddr)->sin6_addr);
	}
}

bool checkLogin(){
	for(unsigned int i=0; i<userDB.size(); i++){
		if(userDB[i]->getLogin() == false){
			return false;
		}
	}
	return true;
}

int main(int argc, char* argv[]) {

	char fileName[] = "UserPassMatch.txt";
	FILE* fileHandle;
	char buffer[MAXLINE];
	char username[MAXLINE];
	char password[MAXLINE];
	char portnumber[MAXLINE];
	ServerRecord* recordPtr;

	int sockFD;
	struct addrinfo hints, *serveInfo, *servePtr;
	int returnValue;
	int yes = 1;

//	struct sigaction sigact;

	struct sockaddr_storage clientAddr;
	socklen_t clientAddrSize;
	int sockFDNew;
	char ipAddr[INET6_ADDRSTRLEN];
	int numByteSend, numByteRec;
	char data[MAXDATASIZE];

	string user, pass, portnum;

	//check the hostname of supernode is import
	if(argc != 2){
		perror("usage: ./Login supernode");
		exit(1);
	}

	//open user record file
	if((fileHandle = fopen(fileName, "r")) == NULL){
		perror("server: fopen has error");
		exit(1);
	}

	//construct the user DB
	while(fgets(buffer, MAXLINE, fileHandle) != NULL){
		if(sscanf(buffer, "%s %s", username, password) == 2){
			recordPtr = new ServerRecord();
			recordPtr->setUsername(string(username));
			recordPtr->setPassword(string(password));
			recordPtr->setLogin(false);
			userDB.push_back(recordPtr);
		}else{
			perror("server: user pass format is not correct");
		}
	}

	//close user record file
	fclose(fileHandle);

	//print the user DB
	for(unsigned int i=0; i<userDB.size(); i++){
		cout<<"username:"<<userDB[i]->getUsername()<<" password:"
				<<userDB[i]->getPassword()<<endl;
	}

	//prepare IP address information
	memset(&hints, 0, sizeof(hints));
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_flags = AI_PASSIVE;

	//get server IP address information
	if((returnValue = getaddrinfo(NULL, SERVER_PORT, &hints, &serveInfo)) != 0){
		fprintf(stderr, "getaddrinfo: %s.\n", gai_strerror(returnValue));
		exit(1);
	}

	for(servePtr = serveInfo; servePtr != NULL; servePtr = servePtr->ai_next){
		//create a socket
		if((sockFD = socket(servePtr->ai_family, servePtr->ai_socktype,
				servePtr->ai_protocol)) == -1){
			perror("server: socket has error");
			continue;
		}
		//setup socket option
		if(setsockopt(sockFD, SOL_SOCKET, SO_REUSEADDR,  &yes, sizeof(int)) == -1){
			perror("server: setsockopt has error");
			exit(1);
		}
		//bind the socket
		if(bind(sockFD, servePtr->ai_addr, servePtr->ai_addrlen) == -1){
			close(sockFD);
			perror("server: bind has error");
			continue;
		}
		break;
	}
	if(servePtr == NULL){
		perror("server: servePtr is NULL");
		exit(1);
	}
	freeaddrinfo(serveInfo);

	if(listen(sockFD, BACKLOG) == -1){
		perror("server: listen has error");
		exit(1);
	}

	/*
	//setup signal between parent and child process
	sigact.sa_handler = sigchld_handler;
	sigemptyset(&sigact.sa_mask);
	sigact.sa_flags = SA_RESTART;
	//register callback function for process signaling
	if(sigaction(SIGCHLD, &sigact, NULL) == -1){
		perror("server: sigaction has error.");
		exit(1);
	}
	*/
	cout<<"login server: waiting for client connections ..."<<endl;

	//accept loop
	while(!checkLogin()){
		clientAddrSize = sizeof(clientAddr);
		sockFDNew = accept(sockFD, (struct sockaddr*)&clientAddr, &clientAddrSize);
		if(sockFDNew == -1){
			perror("server: accept has error");
			continue; //continue to serve next client
		}

		inet_ntop(clientAddr.ss_family, sockAddrTranslate((struct sockaddr*)&clientAddr),
				ipAddr, sizeof(ipAddr));
		cout<<"server: got connection from "<<ipAddr<<endl;


			if((numByteRec = recv(sockFDNew, data, MAXDATASIZE-1, 0)) == -1){
				perror("server: recv has error");
				exit(1);
			}
			data[numByteRec] = '\0';
			cout<<"server: data received is "<<data<<endl;

			if(sscanf(data, "%s %s %s", username, password, portnumber) == 3){
				user = string(username).erase(0, 6);
				pass = string(password);
				portnum = string(portnumber);
			}else{
				perror("server: username/password is missing");
			}

			for(unsigned int i=0; i<userDB.size(); i++){
				if(userDB[i]->getUsername().compare(user) == 0){
					if(userDB[i]->getPassword().compare(pass) == 0){
						if((numByteSend = send(sockFDNew, SUPERNODE_INFO, sizeof(SUPERNODE_INFO), 0)) == -1){
							perror("server: fail to send acceptance");
						}
						userDB[i]->setIPAddr(string(ipAddr));
						userDB[i]->setLogin(true);
						userDB[i]->setPortNum(portnum);
					}
				}
			}

		close(sockFDNew);
	}

	close(sockFD);
	for(unsigned int i=0; i<userDB.size(); i++){
		cout<<userDB[i]->getUsername()<<" "<<userDB[i]->getIPAddr()<<" "<<userDB[i]->getPortNum()<<endl;
	}
	cout<<"Phase 1 is complete"<<endl;

	memset(&hints, 0, sizeof(hints));
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;

	if((returnValue = getaddrinfo(argv[1], SUPER_PORT, &hints, &serveInfo)) != 0){
		fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(returnValue));
		exit(1);
	}

	for(servePtr = serveInfo; servePtr != NULL; servePtr = servePtr->ai_next){
		//construct a socket
		if((sockFD = socket(servePtr->ai_family, servePtr->ai_socktype,
				servePtr->ai_protocol)) == -1){
			perror("server: socket has error");
			continue;
		}
		//connect the socket
		if(connect(sockFD, servePtr->ai_addr, servePtr->ai_addrlen) == -1){
			close(sockFD);
			perror("server: connect has error");
			continue;
		}
		break;
	}
	if(servePtr == NULL){
		perror("server: serverPtr is NULL");
		exit(1);
	}

	//print connected supernode info
	inet_ntop(servePtr->ai_family, sockAddrTranslate((struct sockaddr*)servePtr->ai_addr),
			ipAddr, sizeof(ipAddr));
	cout<<"server: connecting to supernode "<<ipAddr<<endl;
	freeaddrinfo(serveInfo);

	for(unsigned int i=0; i<userDB.size(); i++){
		if((numByteSend = send(sockFD, (userDB[i]->getUsername()+string(" ")+userDB[i]->getIPAddr()+string(" ")+
				userDB[i]->getPortNum()+string("\n")).c_str(),
				userDB[i]->getUsername().size()+userDB[i]->getIPAddr().size()+userDB[i]->getPortNum().size()+3, 0)) == -1){
			perror("server: send has error");
			exit(1);
		}
	}

	cout<<"Phase 2 is complete"<<endl;
	return 0;
}
